﻿/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Xml;

namespace DaveSexton.DocProject.Sandcastle
{
	[Serializable]
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x")]
	public sealed class Help1xProject
	{
		#region Public Properties
		[Browsable(false)]
		public bool IsDirty { get; private set; }

		[DisplayName("Produce Help 1.x"), Description("Indicates whether the project will produce Help 1.x (.chm) output.")]
		public bool OutputEnabled
		{
			get
			{
				return IsEnabled(project);
			}
			set
			{
				if (value != OutputEnabled)
					project.Settings.Write("Sandcastle_ProduceHelp1x", value);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Boolean.TryParse(System.String,System.Boolean@)")]
		private bool ShouldSerializeOutputEnabled()
		{
			bool value;
			bool.TryParse(project.Settings.GetCommittedValue("Sandcastle_ProduceHelp1x"), out value);

			return OutputEnabled != value;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Boolean.TryParse(System.String,System.Boolean@)")]
		private void ResetOutputEnabled()
		{
			bool value;
			bool.TryParse(project.Settings.GetCommittedValue("Sandcastle_ProduceHelp1x"), out value);

			OutputEnabled = value;
		}

		[DisplayName("Include files"), Description("List of files to be included in the compiled help."), 
		Editor(typeof(IncludeFilesEditor), typeof(System.Drawing.Design.UITypeEditor)), TypeConverter(typeof(StringCollectionConverter))]
		public BindableStringCollection IncludeFiles
		{
			get
			{
				EnsureLoaded();
				return files;
			}
		}

		[DisplayName("Binary TOC"), DefaultValue(false), Description("Indicates whether a binary table of contents (TOC) will be used.")]
		public bool BinaryToc
		{
			get
			{
				EnsureLoaded();
				return options.Contains("Binary TOC") && string.Equals(options["Binary TOC"].Value, "Yes", StringComparison.OrdinalIgnoreCase);
			}
			set
			{
				options.Remove("Binary TOC");
				options.Add(new Metadata("Binary TOC", (value) ? "Yes" : "No"));

				IsDirty = true;
			}
		}

		[DisplayName("Binary index"), DefaultValue(true), Description("Indicates whether a binary index will be used.")]
		public bool BinaryIndex
		{
			get
			{
				EnsureLoaded();
				return !options.Contains("Binary Index") || string.Equals(options["Binary Index"].Value, "Yes", StringComparison.OrdinalIgnoreCase);
			}
			set
			{
				options.Remove("Binary Index");
				options.Add(new Metadata("Binary Index", (value) ? "Yes" : "No"));

				IsDirty = true;
			}
		}

		[DisplayName("Automatic index"), DefaultValue(false), 
		Description("Indicates whether keywords embedded in topics will automatically be added to the keyword (K) index.")]
		public bool AutomaticIndex
		{
			get
			{
				EnsureLoaded();
				return options.Contains("Auto Index") && string.Equals(options["Auto Index"].Value, "Yes", StringComparison.OrdinalIgnoreCase);
			}
			set
			{
				options.Remove("Auto Index");
				options.Add(new Metadata("Auto Index", (value) ? "Yes" : "No"));

				IsDirty = true;
			}
		}

		[DisplayName("Default window"), DefaultValue(""),
		Description("Name of the default window that is shown when the compiled help file is opened.")]
		public string DefaultWindow
		{
			get
			{
				EnsureLoaded();
				return (options.Contains("Default Window")) ? options["Default Window"].Value ?? string.Empty : string.Empty;
			}
			set
			{
				options.Remove("Default Window");
				options.Add(new Metadata("Default Window", value));

				IsDirty = true;
			}
		}

		[DisplayName("Default topic"), DefaultValue("{1}"), 
		Description("The default topic that is shown when the compiled help file is opened.  The default value of {1} will be replaced automatically during builds.")]
		public string DefaultTopic
		{
			get
			{
				EnsureLoaded();
				return (options.Contains("Default Topic")) ? options["Default Topic"].Value ?? string.Empty : string.Empty;
			}
			set
			{
				options.Remove("Default Topic");
				options.Add(new Metadata("Default Topic", value));

				IsDirty = true;
			}
		}

		[DisplayName("Full-text search"), DefaultValue(true), 
		Description("Indicates whether full-text search functionality will be supported.")]
		public bool FullTextSearch
		{
			get
			{
				EnsureLoaded();
				return options.Contains("Full-text search") && string.Equals(options["Full-text search"].Value, "Yes", StringComparison.OrdinalIgnoreCase);
			}
			set
			{
				options.Remove("Full-text search");
				options.Add(new Metadata("Full-text search", (value) ? "Yes" : "No"));

				IsDirty = true;
			}
		}

		[DisplayName("Display progress during build"), DefaultValue(false), 
		Description("Indicates whether information about the individual topics being processed should be written to the output window during builds.")]
		public bool DisplayProgressDuringBuild
		{
			get
			{
				EnsureLoaded();
				return options.Contains("Display compile progress") && string.Equals(options["Display compile progress"].Value, "Yes", StringComparison.OrdinalIgnoreCase);
			}
			set
			{
				options.Remove("Display compile progress");
				options.Add(new Metadata("Display compile progress", (value) ? "Yes" : "No"));

				IsDirty = true;
			}
		}
		#endregion

		#region Private / Protected
		private static readonly List<string> ignoreOptions = new List<string>
		{
			"Compiled file", 
			"Contents file", 
			"Index file", 
			"Language", 
			"Title"
		};

		[NonSerialized]
		private readonly IDocProject project;
		private readonly string settingsFile;
		private readonly MetadataCollection options;
		[NonSerialized]
		private readonly BindableStringCollection files;
		[NonSerialized]
		private readonly Dictionary<string, XmlNode> optionNodes;
		[NonSerialized]
		private XmlDocument document;
		[NonSerialized]
		private XmlNode optionsHeaderNode, filesFooterNode, filesHeaderNode;
		private bool loaded;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="Help1xProject" /> class.
		/// </summary>
		public Help1xProject(IDocProject project, string settingsFile)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (string.IsNullOrEmpty(settingsFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "settingsFile");

			this.project = project;
			this.settingsFile = settingsFile;
			options = new MetadataCollection();
			files = new BindableStringCollection();
			optionNodes = new Dictionary<string, XmlNode>();

			files.ListChanged += (s, e) => IsDirty = true;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Help1xProject" /> class.
		/// </summary>
		public Help1xProject(Help1xProject clone, bool cloneIsDirty)
		{
			if (clone == null)
				throw new ArgumentNullException("clone");

			project = clone.project;
			settingsFile = clone.settingsFile;
			options = new MetadataCollection(clone.options);
			files = new BindableStringCollection(clone.files);

			files.ListChanged += (s, e) => IsDirty = true;

			loaded = clone.loaded;
			document = clone.document;
			optionNodes = new Dictionary<string, XmlNode>(clone.optionNodes);
			optionsHeaderNode = clone.optionsHeaderNode;
			filesHeaderNode = clone.filesHeaderNode;
			filesFooterNode = clone.filesFooterNode;

			if (cloneIsDirty)
				IsDirty = clone.IsDirty;
		}
		#endregion

		#region Methods
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
		public static bool IsEnabled(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			return project.Settings.Read("Sandcastle_ProduceHelp1x", false);
		}

		private void EnsureLoaded()
		{
			if (!loaded)
			{
				Load();
				loaded = true;
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.EndsWith(System.String)")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.StartsWith(System.String)")]
		private void Load()
		{
			document = new XmlDocument();
			document.PreserveWhitespace = true;

			try
			{
				document.Load(settingsFile);
			}
			catch (System.IO.DirectoryNotFoundException) { }
			catch (System.IO.FileNotFoundException) { }
			catch (System.Xml.XmlException ex)
			{
				System.Diagnostics.Debug.Write(ex);
			}

			XmlNode root = document.DocumentElement;

			if (root == null)
				root = document.AppendChild(document.CreateElement("configuration"));

			XmlNode template = root.SelectSingleNode("hhpTemplate");

			if (template == null)
				template = root.AppendChild(document.CreateElement("hhpTemplate"));

			bool loadingOptions = false, loadingFiles = false;

			foreach (XmlElement line in template.SelectNodes("line"))
			{
				if (line.Name != "line")
					continue;

				if (line.InnerText == "[OPTIONS]")
				{
					optionsHeaderNode = line;
					loadingFiles = false;
					loadingOptions = true;
				}
				else if (line.InnerText == "[FILES]")
				{
					filesHeaderNode = line;
					loadingFiles = true;
					loadingOptions = false;
				}
				else if (line.InnerText.StartsWith("[") && line.InnerText.EndsWith("]"))
				{
					if (loadingFiles)
						filesFooterNode = line;

					loadingOptions = false;
					loadingFiles = false;
				}
				else if (loadingOptions)
				{
					string[] option = line.InnerText.Split('=');

					if (option.Length > 0)
					{
						string name = option[0];

						if (!string.IsNullOrEmpty(name) && !ignoreOptions.Exists(s => string.Equals(s, name, StringComparison.OrdinalIgnoreCase)))
						{
							optionNodes.Add(name, line);
							options.Remove(name);
							options.Add(new Metadata(name, (option.Length > 1) ? option[1] : null));
						}
					}
				}
				else if (loadingFiles)
				{
					string file = line.InnerText;

					if (!string.IsNullOrEmpty(file))
						files.Add(file);
				}
			}
		}

		public void Save()
		{
			if (IsDirty)
			{
				if (loaded)
				{
					IProjectItem item = project.GetItem(settingsFile);
					item.Checkout();

					XmlNode template = document.DocumentElement.SelectSingleNode("hhpTemplate");

					foreach (Metadata data in options)
					{
						XmlNode node;

						if (optionNodes.ContainsKey(data.Name))
							node = optionNodes[data.Name];
						else
						{
							node = template.InsertAfter(document.CreateElement("line"), optionsHeaderNode);
							template.InsertBefore(document.CreateTextNode(Environment.NewLine), node);
						}

						node.InnerText = data.Name + "=" + data.Value;
					}

					if (filesHeaderNode != null)
					{
						XmlNode firstFile = filesHeaderNode.NextSibling;

						if (firstFile != null && firstFile != filesFooterNode)
						{
							if (filesFooterNode != null)
							{
								if (firstFile == filesFooterNode.PreviousSibling)
									template.RemoveChild(firstFile);
								else
									firstFile.CreateNavigator().DeleteRange(filesFooterNode.PreviousSibling.CreateNavigator());
							}
							else
							{
								do
								{
									XmlNode node = firstFile.NextSibling;
									template.RemoveChild(firstFile);
									firstFile = node;
								}
								while (firstFile != null);
							}
						}
					}
					else
					{
						filesHeaderNode = template.AppendChild(document.CreateElement("line"));
						filesHeaderNode.InnerText = "[FILES]";
					}

					XmlNode currentNode = filesHeaderNode;

					foreach (string file in files)
					{
						currentNode = template.InsertAfter(document.CreateElement("line"), currentNode);
						currentNode.InnerText = file;

						template.InsertBefore(document.CreateTextNode(Environment.NewLine), currentNode);
					}

					template.InsertAfter(document.CreateTextNode(Environment.NewLine), currentNode);

					document.Save(settingsFile);

					item.Show();
				}

				IsDirty = false;
			}
		}
		#endregion

		#region Nested
		private sealed class IncludeFilesEditor : System.Drawing.Design.UITypeEditor
		{
			#region Public Properties
			public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
			{
				return System.Drawing.Design.UITypeEditorEditStyle.None;
			}
			#endregion

			#region Private / Protected
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="IncludeFilesEditor" /> class.
			/// </summary>
			public IncludeFilesEditor()
			{
			}
			#endregion

			#region Methods

			#endregion
		}
		#endregion
	}
}
